---
title: Тестирование VKUI-приложений
date: 2024/12/24
description: Разберём основные принципы тестирования приложений на базе VKUI.
tags: testing
image: /blog/vkui-testing.png
---

import { PostHeader } from '@vkontakte/vkui-docs-theme';

<PostHeader frontMatter={metadata} />

## Тестирование с помощью Jest и React Testing Library

Несмотря на то, что существует несколько различных инструментов по тестированию React-приложений,
мы рекомендуем использовать `Jest` и `React Testing Library` для функционального или unit-тестирования VKUI-приложений.
В данном руководстве мы рассмотрим основные подходы к тестированию, используя данный стек технологий.
Его же мы применяем сами для тестирования компонентов VKUI.

<Callout>
  Предполагается, что вы уже обладаете базовыми знаниями работы с выбранными инструментами, если это
  не так, то обратитесь к документации [Jest](https://jestjs.io/) и [React Testing
  Library](https://testing-library.com/docs/react-testing-library/intro). В данном руководстве мы
  сосредоточимся именно на специфичных настройках под VKUI.
</Callout>

### Jest

Специфичной настройки `Jest` для тестирования VKUI-компонентов не требуется, но могут возникнуть трудности
с рядом компонентов, которые опираются на браузерное API. Подробнее об этом написано в [секции ниже](#special-components).

Для корректной работы VKUI-компонентов необходимо использовать обязательную обёртку, об этом упоминается
в [Быстром старте](/overview/quick-start#step4).

### React Testing Library

Для того, чтобы тесты было проще поддерживать, рекомендуется избегать завязки на детали имплементации компонентов.
Не полагаясь на внутреннее устройство, вы делаете свои тесты более устойчивыми к изменениям структуры в VKUI,
которые могут произойти даже в рамках минорных изменений.
В VKUI мы стараемся снабжать все компоненты функциональными ролями и атрибутами или обеспечивать их поддержку,
поэтому в связке с `React Testing Library`, которая позволяет работает с настоящим DOM-представлением, вы можете
взаимодействовать с компонентами максимально близко к пользовательскому опыту.

Например, компонент `Checkbox` по умолчанию имеет `role="checkbox"`, поэтому вы можете найти этот компонент,
используя специальное API, предоставляемое `React Testing Library`:

```jsx
import { render, screen } from '@testing-library/react';

test('best practices of element accesing', () => {
  // ✅ - используем функциональную роль
  render(<Checkbox>Text</Checkbox>);
  const checkbox = screen.getByRole('checkbox');

  // ❌ - завязываемся на класс, который может измениться
  render(<Checkbox>Text</Checkbox>);
  const checkbox = document.querySelector('.CheckboxInput__input');

  // write your test...
});
```

Обратите внимание, что в случае, если компонент представляет собой композицию более мелких компонентов,
не всегда возможно опираться на функциональную роль или атрибут, поэтому в компонент есть возможность прокинуть
`data-testid` для нужной части. Например:

```jsx
import { render, screen } from '@testing-library/react';

test('check if prevButton disabled', () => {
  render(<Pagination prevButtonTestId="prevButton" currentPage={1} totalPages={10} />);

  const prevButton = screen.getByTestId('prevButton');
  expect(prevButton).toBeDisabled();
});
```

Если вы все-таки не можете протестировать нужную часть без необходимости завязываться на, например,
внутренний класс, создайте нам [feature request](https://github.com/VKCOM/VKUI/issues/new/choose) на Github.

## Особые компоненты [#special-components]

### Snackbar, Spinner, SplitCol

Некоторые компоненты зависят от браузерного API, которое не реализовано в окружении `Jest`, поэтому для
тестирования подобных компонентов вам необходимо предоставить `mock`-реализацию самостоятельно.
Подробнее читайте в [этом руководстве](https://jestjs.io/docs/manual-mocks).

### Select

Так как VKUI стремится к мимикрии под различные платформы, некоторые компоненты могут существенно отличаться
в структуре. Например, это справедливо для компонента `Select`, который на мобильных платформах (`android/iOS`)
представляет собой нативный элемент и на десктопе его кастомную реализацию.

Из-за поддержки SSR мы не всегда можем заранее знать, какое представление необходимо отрисовать.
Если попытаться отрендерить `Select` без указания платформы, то в DOM-представлении у нас окажется две реализации.
Чтобы избежать ошибок, проще всего обернуть `Select` в `PlatformProvider` с указанием `vkcom` платформы для
`web`-реализации или android для мобильной платформы.

```jsx
import { render } from '@testing-library/react';
import { PlatformProvider, Select } from '@vkontakte/vkui';

test('test web view', () => {
  render(
    <PlatformProvider value="vkcom">
      <Select id="select-id" options={[]} {...props} />
    </PlatformProvider>,
  );

  // write your test...
});

test('test mobile view', () => {
  render(
    <PlatformProvider value="android">
      <Select id="select-id" options={[]} {...props} />
    </PlatformProvider>,
  );

  // write your test...
});
```

Для тестирования мобильного представления, вы можете воспользоваться стандартным API от `React Testing Library` и `Testing Library`:

```jsx
import { render, screen } from '@testing-library/react';
import { userEvent } from './testing/helpers';
import { PlatformProvider, Select } from '@vkontakte/vkui';

test('test is value selected on mobile', () => {
  render(
    <PlatformProvider value="android">
      <Select
        data-testid="target"
        defaultValue="1"
        options={[
          { value: '0', label: 'Mike' },
          { value: '1', label: 'Josh' },
        ]}
      />
    </PlatformProvider>,
  );

  await userEvent.selectOptions(screen.getByTestId('target'), ['0']);
  expect(screen.getByTestId('target')).toHaveValue('0');
});
```

<Callout>
  Обратите внимание, мы не используем функцию `userEvent` напрямую из `@testing-library/user-event`,
  вместо этого импортируется обёртка, которая адаптирует функцию под работу в `Jest`-окружении:

```jsx filename="./testing/helpers"
import userEventLib from '@testing-library/user-event';
/**
 * Переконфигурируем работу userEvent под jest
 *
 * https://github.com/testing-library/user-event/issues/833
 */
export const userEvent = userEventLib.setup({ advanceTimers: jest.advanceTimersByTime });
```

</Callout>

Для тестирования `web`-реализации воспользуйтесь более низкоуровневым `API` от `React Testing Library`:

```jsx
import { render, fireEvent, screen } from '@testing-library/react';
import { PlatformProvider, Select } from '@vkontakte/vkui';

test('test is value selected on web', () => {
  render(
    <PlatformProvider value="vkcom">
      <Select
        labelTextTestId="labelTextTestId"
        defaultValue="0"
        options={[
          { value: '0', label: 'Mike' },
          { value: '1', label: 'Josh' },
        ]}
      />
    </PlatformProvider>,
  );

  fireEvent.click(screen.getByTestId('labelTextTestId'));
  const unselectedOption = screen.getByRole('option', { selected: false, name: 'Josh' });
  fireEvent.mouseEnter(unselectedOption);
  fireEvent.click(unselectedOption);

  expect(screen.getByTestId('labelTextTestId').textContent).toEqual('Josh');
});
```

<Callout>
  Обратите внимание, выше мы используем `labelTextTestId` вместо просто `data-testid`, это позволяет
  эмулировать работу мыши или тач-устройства и быстрее получить доступ к выбранной опции.

</Callout>
